using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Windows.Forms;

namespace System.Windows.Forms.ColorControls
{
    /// <summary>
    /// Summary description for ColorPickerBase.
    /// </summary>
    public class ColorPickerBase : System.Windows.Forms.UserControl
    {
        const int clrBoxPadding = 2;
        const int clrBoxWidth = 20;
        const bool defaultAutoSize = true;
        const BorderStyle defaultBorderStyle = BorderStyle.Fixed3D;
        const bool defaultDisplayColor = true;
        const bool defaultDisplayColorName = true;

        static readonly Size defaultBorderSize = SystemInformation.BorderSize;

        private bool bDisplayColor = defaultDisplayColor;
        private bool bDisplayColorName = defaultDisplayColorName;
        private Size borderSize = defaultBorderSize;
        private BorderStyle borderStyle = defaultBorderStyle;
        private ButtonState buttonState = ButtonState.Normal;

        // The creation of the drop down panel is deferred until the user
        // click the button.  So we store properties locally and then apply them
        // to the panel after it's created.
        private Color color = ColorPanel.defaultColor;
        private Rectangle comboButtonRectangle;

        /// <summary> 
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.Container components = null;
        private Size padding = new Size(1,2);
        private PopupForm popup = null;

        public ColorPickerBase()
        {
            // This call is required by the Windows.Forms Form Designer.
            InitializeComponent();

            SetStyle(ControlStyles.CacheText, true);
            SetBorderSize();
        }

        /// <summary>
        /// The ColorChangedEvent event handler.
        /// </summary>
        [Browsable(true),
        Category("ColorPicker")]
        public event ColorChangedEventHandler ColorChanged;

        /// <summary>
        /// BackColor
        /// </summary>
        [DefaultValue(typeof(Color), "Window")]
        public override System.Drawing.Color BackColor
        {
            get { return base.BackColor; }
            set { base.BackColor = value; }
        }

        /// <summary>
        /// Sets/gets the control's BorderStyle.
        /// </summary>
        [Browsable(true),
        Category("Appearance")]
        [Description("Indicates the border style of the picker control.")]
        [DefaultValue(defaultBorderStyle)]
        public new System.Windows.Forms.BorderStyle BorderStyle
        {
            get
            {
                return base.BorderStyle;
            }
            set
            {
                base.BorderStyle = value;

                SetBorderSize();

                comboButtonRectangle = new Rectangle();

                SetControlSize();
            }
        }

        /// <summary>
        /// Set/get the pick color.
        /// </summary>
        [Browsable(true),
        Category("ColorPicker"),
        Description("Get/set the pick color.")]
        public System.Drawing.Color Color
        {
            get
            {
                return color;
            }
            set
            {
                color = value;
                OnColorChanged(new ColorChangedEventArgs(value));
                Refresh();
            }
        }

        /// <summary>
        /// Show/hide the selected color in the combo-box part of the control.
        /// </summary>
        [Browsable(true),
        Category("ColorPicker"),
        DefaultValue(defaultDisplayColor)]
        [Description("Show/hide the selected color in the combo-box part of the control.")]
        public bool DisplayColor
        {
            get
            {
                return bDisplayColor;
            }
            set
            {
                if (value != bDisplayColor)
                {
                    bDisplayColor = value;
                    Refresh();
                }
            }
        }

        /// <summary>
        /// Show/hide the name of the selected color in the text part of the combo-box.<br></br>
        /// Otherwise show the value of the Text property.
        /// </summary>
        [Browsable(true),
        Category("ColorPicker"),
        DefaultValue(defaultDisplayColorName)]
        [Description("Show/hide the name of the selected color in the text part of the combo-box.")]
        public bool DisplayColorName
        {
            get
            {
                return bDisplayColorName;
            }
            set
            {
                if (value != bDisplayColorName)
                {
                    bDisplayColorName = value;
                    Refresh();
                }
            }
        }

        /// <summary>
        /// The _Text property is the same as the Text property.<br></br><br></br>
        /// This property has been provided because there seems to be a 
        /// problem with UserControl.Text in Visual Studio beta 2, 
        /// whereby values set in the property tab are lost.
        /// </summary>
        [Browsable(true),
        Category("ColorPicker"),
        Description("Set/get the text to be displayed.")]
        public string _Text
        {
            get
            {
                return base.Text;
            }
            set
            {
                base.Text = value;
                Refresh();
            }
        }

        /// <summary>
        /// Design time support to reset the Color property to it's default value.
        /// </summary>
        public void ResetColor()
        {
            Color = ColorPanel.defaultColor;
        }

        /// <summary>
        /// Design time support to reset the _Text property to it's default value.
        /// </summary>
        public void Reset_Text()
        {
            base.Text = "";
        }

        /// <summary>
        /// Design time support to indicate whether the Color property should be serialized.
        /// </summary>
        /// <returns></returns>
        public bool ShouldSerializeColor()
        {
            return Color != ColorPanel.defaultColor;
        }

        /// <summary>
        /// Design time support to indicate whether the _Text property should be serialized?
        /// </summary>
        /// <returns></returns>
        public bool ShouldSerialize_Text()
        {
            return base.Text != "";
        }

        /// <summary>
        /// Display the ColorPicker's drop down palette.
        /// </summary>
        public virtual void ShowDropdown()
        {
            if (popup == null)
                popup = CreatePopup();

            if (popup.Visible)
                popup.Pop();
            else
            {
                PrePopup(popup);
                popup.Show(this);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        protected virtual PopupForm CreatePopup()
        {
            return null;
        }

        /// <summary> 
        /// Clean up any resources being used.
        /// </summary>
        protected override void Dispose( bool disposing )
        {
            if( disposing )
            {
                if(components != null)
                {
                    components.Dispose();
                }
            }
            base.Dispose( disposing );

            if (null != popup)
            {
                popup.Dispose();
                popup = null;
            }
        }

        /// <summary>
        /// Overrides IsInputKey.<br></br><br></br>
        /// This allows the control to tell the base class that the keys
        /// Keys.Down or Keys.Down + Keys.Alt should cause the OnKeyDown event.
        /// </summary>
        /// <param name="keyData">One of the <c>System.Windows.Forms.Keys</c> values</param>
        /// <returns><B>true</B> if keyData is one of 
        /// Keys.Down or Keys.Down+Keys.Alt.  Otherwise <B>false</B>.</returns>
        protected override bool IsInputKey(System.Windows.Forms.Keys keyData)
        {
            bool bIsInputKey = true;

            if ((keyData == Keys.Down) || (keyData == (Keys.Down | Keys.Alt)))
            {
                ShowDropdown();
            }
            else
            {
                bIsInputKey = base.IsInputKey(keyData);
            }

            return bIsInputKey;
        }

        /// <summary>
        /// Raises the ColorChanged event.
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnColorChanged(ColorChangedEventArgs e)
        {
            if (null != ColorChanged)
            {
                ColorChanged(this, e);
            }

            Refresh();
        }

        /// <summary>
        /// Overrides OnEnabledChanged so the control can redraw itself 
        /// enabled/disabled.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnEnabledChanged(System.EventArgs e)
        {
            base.OnEnabledChanged(e);

            buttonState = (Enabled) ? ButtonState.Normal : ButtonState.Inactive;

            Refresh();
        }

        /// <summary>
        /// Overrides OnFontChanged so the control can resize/redraw itself appropriately.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnFontChanged(System.EventArgs e)
        {
            SetControlSize();
            Refresh();
        }

        /// <summary>
        /// Overrides OnGotFocus so the control can be redrawn with the focus.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnGotFocus(System.EventArgs e)
        {
            System.Diagnostics.Trace.WriteLine("ColorPicker.OnGotFocus");
            base.OnGotFocus(e);
            Refresh();
        }

        /// <summary>
        /// Overrides OnLostFocus so the control can be redrawn without the focus.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnLostFocus(System.EventArgs e)
        {
            System.Diagnostics.Trace.WriteLine("ColorPicker.OnLostFocus");

            if (buttonState == ButtonState.Pushed)
            {
                buttonState = ButtonState.Normal;
                if (popup.Visible)
                    popup.Pop();

                Invalidate(GetComboButtonRectangle(), false);
                Update();
            }

            base.OnLostFocus(e);
            Refresh();
        }

        /// <summary>
        /// Overrides OnMouseDown.
        /// </summary>
        /// <remarks>
        /// Checks if the MouseDown event occured on the combo button.  If
        /// so the control is redrawn in the pushed state.</remarks>
        /// <param name="e"></param>
        protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e)
        {
            System.Diagnostics.Trace.WriteLine("ColorPicker.OnMouseDown");
            base.OnMouseDown(e);

            if (Enabled)
            {
                buttonState = ButtonState.Flat;

                Invalidate(GetComboButtonRectangle(), false);
                Update();

                ShowDropdown();
            }
        }

        /// <summary>
        /// Overrides OnMouseUp.
        /// </summary>
        /// <remarks>
        /// When the user releases the mouse over the combo button after having pressed it, the 
        /// contained <see cref="ColorPanel">ColorPanel</see> is displayed.
        /// </remarks>
        /// <param name="e"></param>
        protected override void OnMouseUp(System.Windows.Forms.MouseEventArgs e)
        {
            System.Diagnostics.Trace.WriteLine("ColorPicker.OnMouseUp");
            base.OnMouseUp(e);

            if (buttonState != ButtonState.Normal)
            {
                buttonState = ButtonState.Normal;

                Invalidate(GetComboButtonRectangle(), false);
                Update();
            }
        }

        /// <summary>
        /// Overrides OnPaint so the control can be drawn.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
        {
            switch(borderStyle)
            {
                case BorderStyle.Fixed3D:
                    ControlPaint.DrawBorder3D(e.Graphics, ClientRectangle, Border3DStyle.Sunken, Border3DSide.All);
                    break;
                case BorderStyle.FixedSingle:
                    ControlPaint.DrawBorder3D(e.Graphics, ClientRectangle, Border3DStyle.Flat, Border3DSide.All);
                    break;
            }

            // fill rectangle with window color or control color if disabled
            Color color_background = (Enabled) ? (base.BackColor) : Color.FromKnownColor(KnownColor.Control);
            SolidBrush br = new SolidBrush(color_background);

            Rectangle r = ClientRectangle;
            r.Inflate(-borderSize.Width, -borderSize.Height);
            e.Graphics.FillRectangle(br, r);

            if (Focused && Enabled)
            {
                // add focus rectangle
                Rectangle rh = new Rectangle(r.Location, new Size(r.Width-GetComboButtonRectangle().Width, r.Height));
                rh.Inflate(-1,-1);
                br.Color = Color.FromKnownColor(KnownColor.Highlight);
                e.Graphics.FillRectangle(br,rh);
            }

            // draw rectangle of pick color
            Point text_p;

            if (bDisplayColor)
            {
                // draw a rectangle of the chosen color
                br.Color = color;
                Rectangle r1 = r;
                r1.Inflate(-clrBoxPadding, -clrBoxPadding);
                r1.Width = clrBoxWidth;

                ControlPaint.DrawBorder3D(e.Graphics, r1, Border3DStyle.Flat, Border3DSide.All);
                r1.Inflate(-1, -1);
                e.Graphics.FillRectangle(br, r1);

                text_p = new Point(r.Left + clrBoxWidth + clrBoxPadding + padding.Width, (ClientRectangle.Height - Font.Height)/2);
            }
            else
            {
                text_p = new Point(r.Left + padding.Width, (ClientRectangle.Height - Font.Height)/2);
            }

            // draw text in fore color or control dark if disabled
            br.Color =
                (Enabled) ? ((Focused) ? Color.FromKnownColor(KnownColor.HighlightText) : (this.ForeColor)) :	(Color.FromKnownColor(KnownColor.ControlDark));

            string text = "";
            if (!bDisplayColorName)
                text = base.Text;
            else if (color.IsKnownColor)
                text = color.Name;
            else
                text = String.Format("{0},{1},{2}", color.R, color.G, color.B);

            e.Graphics.DrawString(text, this.Font, br, text_p);

            // draw combo button
            ControlPaint.DrawComboButton(e.Graphics, GetComboButtonRectangle(), buttonState);

            // clean up brushes
            br.Dispose();
            br = null;

            base.OnPaint(e);
        }

        /// <summary>
        /// Overrides OnPaintBackground.  Since the whole of the control is drawn
        /// in OnPaint, OnPaintBackground is overridden to do nothing.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnPaintBackground(System.Windows.Forms.PaintEventArgs e)
        {
            // no need to paint background
        }

        /// <summary>
        /// Override OnResize.
        /// </summary>
        /// <remarks>
        /// OnResize is overridden in order to autosize the control.<br></br><br></br>
        /// 
        /// The control's width may be set freely but if the AutoSize property is set to <b>true</b>
        /// the control's height is fixed to Font.Height + Border.Height + 4.
        /// </remarks>
        /// <param name="e"></param>
        protected override void OnResize(System.EventArgs e)
        {
            base.OnResize(e);

            SetControlSize();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="popup"></param>
        protected virtual void PrePopup(PopupForm popup)
        {
        }

        private Rectangle GetComboButtonRectangle()
        {
            if (comboButtonRectangle.IsEmpty)
            {
                int comboButtonHeight = ClientRectangle.Height - borderSize.Height*2;
                int comboButtonWidth  = SystemInformation.VerticalScrollBarWidth;

                comboButtonRectangle = new Rectangle(
                    ClientRectangle.Right - comboButtonWidth - borderSize.Width,
                    ClientRectangle.Top + borderSize.Height,
                    comboButtonWidth,
                    comboButtonHeight);
            }

            return comboButtonRectangle;
        }

        /// <summary> 
        /// Required method for Designer support - do not modify 
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            //
            // ColorPickerBase
            //
            this.BackColor = System.Drawing.SystemColors.Window;
            this.Name = "ColorPickerBase";
            this.Size = new System.Drawing.Size(150, 24);
        }

        private bool MouseOnComboButton(System.Windows.Forms.MouseEventArgs e)
        {
            if (MouseButtons.Left == e.Button)
            {
                return GetComboButtonRectangle().Contains(e.X, e.Y);
            }
            else
            {
                return false;
            }
        }

        private void SetBorderSize()
        {
            Size borderSize = new Size();

            switch (borderStyle)
            {
                case BorderStyle.FixedSingle:
                    borderSize = SystemInformation.BorderSize;
                    break;
                case BorderStyle.None:
                    break;
                case BorderStyle.Fixed3D:
                default:
                    borderSize = SystemInformation.Border3DSize;
                    break;
            }

            this.borderSize  = borderSize;
        }

        private void SetControlSize()
        {
            if (base.AutoSize)
            {
                int h = FontHeight + 2*padding.Height + 2*borderSize.Height;
                int w = this.Width;

                if (w < SystemInformation.VerticalScrollBarWidth + borderSize.Width*2)
                {
                    w = SystemInformation.VerticalScrollBarWidth + borderSize.Width*2 ;
                }

                this.ClientSize = new Size(w,h);
            }

            comboButtonRectangle = new Rectangle();

            Refresh();
        }
    }
}